之前寫了一個很簡單的 Program Loader,
現在就來真正的實作它,讓它能夠把編譯好的程式放到指定位置吧。
主要參考對象是我們的老朋友:RISC-V-TLM
大部分的 SOC 都會有一些開機的流程,
例如先把程式燒進去指定裝置的指定位置上,
然後就是多個階段的 Boot Loader,
第一階段被 Hardware 的機制 Load 起來,
第二階段被第一階段 Loader Load 起來,
每一階段都做一點準備 (設定 Externam Device Register, 啟動 MMU 等),
最後把主程式 Load 進記憶體開始執行,
大概會有個 2~3 階段。
目前這個模擬器規劃比較簡單,
因為模擬環境目前沒有需要提前設定的設計,
也沒有 Binary Code 大小的限制,
一開機就可以直接跳進主程式執行,
所以只要有辦法把主程式放到指定位置,
再讓 Cpu 從那邊開始執行就可以了!
github 頁面 Tag: ITDay27
開場先處理 Load File 失敗的問題,
讓 Simulator 在讀到非法指令的時後就結束。
//executor.cpp
default:
cpu->raise_exception(CPU_INTERFACE::ILLEGAL_INSTRUCTION_EXCEPTION_CAUSE);
break;
為簡化開發流程,loadBinaeyFromHex
目前只支援特定檔名與檔案格式,
之後擴充的時候再支援指定路徑的部分。
//memory.h
...
void loadBinaeyFromHex(std::string filePath = "./binary.hex");
...
Loader 大致上跟 RISC-V-TLM 的做法一樣,
這邊將 hexFile.is_open()
和 line[0] != ':'
改成判斷完先處理,
而不是一層一層包起來,稍微提升可讀性。
嘗試用 std::map + Switch Statement 簡化每次比較字串的流程,
不需要每次都寫 else if(std::stoul(line.substr(7, 2)) == "02")
,
但看起來可讀性沒有比較好,反而是執行效率有機會變好一點。
修掉了奇怪的 * 16
,
RISC-V-TLM 在 "03" 的 case 有一段程式碼:code_segment = stol(line.substr(9, 4), nullptr, 16) * 16; /* ? */
最後那段 * 16; /* ? */
看起來是不小心把某個 Memory 的位置放錯了,
導致需要用特別行為的處理,
但在這邊沒有這個問題,改回正常運算流程。
//memory.cpp
...
void MEMORY::loadBinaeyFromHex(std::string filePath)
{
std::ifstream hexFile(filePath);
std::string line = "";
if(!hexFile.is_open()) {
std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
std::cout << "Binary File Open Failed!!" << std::endl;
std::cout << "Cause: " << std::strerror(errno) << std::endl;
std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
return;
}
uint32_t extended_address = 0;
uint32_t memory_offset = 0;
uint32_t program_counter = 0;
dataMemory.assign(0x100000, 0);
while(std::getline(hexFile, line)) {
if(line[0] != ':') {
continue; //skip this line
}
switch (std::stoul(line.substr(7, 2))) {
case 0: { //Data
auto byteCount = std::stol(line.substr(1, 2), nullptr, 16);
auto address = extended_address + std::stoul(line.substr(3, 4), nullptr, 16);
for(int i=0; i < byteCount; i++) {
auto value = std::stoul(line.substr(9 + (i*2), 2), nullptr, 16);
dataMemory[address + i] = value;
std::cout << "00" << " address: 0x" << address + i << " value: 0x" << std::hex << value << std::endl;
}
}
break;
case 2: { //Extended segment address
extended_address = std::stoul(line.substr(9, 4), nullptr, 16);
}
break;
case 3: { //Start segment address
uint32_t code_segment = stoul(line.substr(9, 4), nullptr, 16);
program_counter = code_segment + stoul(line.substr(13, 4), nullptr, 16);
std::cout << "03 " << "program counter should be: 0x" << std::hex << program_counter << std::endl;
}
break;
case 4: { //Start srgmant address
memory_offset = stoul(line.substr(9, 4), nullptr, 16) << 16;
extended_address = 0;
}
break;
case 5: { //Get start program counter
program_counter = stol(line.substr(9, 8), nullptr, 16);
std::cout << "05 " << "program counter should be: 0x" << std::hex << program_counter << std::endl;
}
break;
default:
break;
}
}
}
...
這次測試直接把 Program Counter 寫死到指定位置,
預計把 Program Loader 從 Memory 獨立出去之後,再讓 Cpu 可以從正確位置開始執行。
程式碼來源則是之前先做好的 Cross Compiler 編譯結果,
之後會再開一篇說明,
有興趣的可以先照 RISC-V-TLM 的流程建立編譯環境。
另外注意如果是虛擬環境(VMware等等),要記得把容量和記憶體調高,
不然會載不下 gcc + 編譯 gcc 的過程會因為記憶體不足碰到奇怪的問題。
現在才發現 RISC-V-TLM 有點奇怪,
我們從從第一道指令執行結果就不一樣了!
導致我的第二道指令存取到不合法的位置。
看來要找一下 RISC-V-TLM 一開始到底做了些什麼,
不然沒道理 ADDI 一開始 x2 - 32
會變成 0x3ffffdf
,x2
到底存了什麼神奇數字?又是從哪邊來的呢?
我們明天好好研究一下吧!
//RISCV-SIM by hsufit
current_pc: 0x1054 target_pc: 0x1058 ADDI 2 0 2 rs1Value: 0xffffffe0 rs2Value: 0x0 rdValue: 0xffffffe0 immValue: 0xffffffe0
error at address: 0xfffffffc!!
current_pc: 0x1058 target_pc: 0x105c SW 2 8 28 rs1Value: 0xffffffe0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x1c
//RISC-V-TLM by mariusmm
time 0 s: PC: 0x10054. time 0 s: ADDI: x2 + -32 -> x2(0x3ffffdf)
time 10 ns: PC: 0x10058. time 10 ns: SW: x8(0x0) -> x2 + 0x1c (@0x3fffffb)